import { mac } from './browser.js';
import { indexOf } from './misc.js';

// EVENT HANDLING

// Lightweight event framework. on/off also work on DOM nodes,
// registering native DOM handlers.

const noHandlers = [];

export let on = function (emitter, type, f) {
  if (emitter.addEventListener) {
    emitter.addEventListener(type, f, false);
  } else if (emitter.attachEvent) {
    emitter.attachEvent('on' + type, f);
  } else {
    let map = emitter._handlers || (emitter._handlers = {});
    map[type] = (map[type] || noHandlers).concat(f);
  }
};

export function getHandlers(emitter, type) {
  return (emitter._handlers && emitter._handlers[type]) || noHandlers;
}

export function off(emitter, type, f) {
  if (emitter.removeEventListener) {
    emitter.removeEventListener(type, f, false);
  } else if (emitter.detachEvent) {
    emitter.detachEvent('on' + type, f);
  } else {
    let map = emitter._handlers,
      arr = map && map[type];
    if (arr) {
      let index = indexOf(arr, f);
      if (index > -1) map[type] = arr.slice(0, index).concat(arr.slice(index + 1));
    }
  }
}

export function signal(emitter, type /*, values...*/) {
  let handlers = getHandlers(emitter, type);
  if (!handlers.length) return;
  let args = Array.prototype.slice.call(arguments, 2);
  for (let i = 0; i < handlers.length; ++i) handlers[i].apply(null, args);
}

// The DOM events that CodeMirror handles can be overridden by
// registering a (non-DOM) handler on the editor for the event name,
// and preventDefault-ing the event in that handler.
export function signalDOMEvent(cm, e, override) {
  if (typeof e == 'string')
    e = {
      type: e,
      preventDefault: function () {
        this.defaultPrevented = true;
      },
    };
  signal(cm, override || e.type, cm, e);
  return e_defaultPrevented(e) || e.codemirrorIgnore;
}

export function signalCursorActivity(cm) {
  let arr = cm._handlers && cm._handlers.cursorActivity;
  if (!arr) return;
  let set = cm.curOp.cursorActivityHandlers || (cm.curOp.cursorActivityHandlers = []);
  for (let i = 0; i < arr.length; ++i) if (indexOf(set, arr[i]) == -1) set.push(arr[i]);
}

export function hasHandler(emitter, type) {
  return getHandlers(emitter, type).length > 0;
}

// Add on and off methods to a constructor's prototype, to make
// registering events on such objects more convenient.
export function eventMixin(ctor) {
  ctor.prototype.on = function (type, f) {
    on(this, type, f);
  };
  ctor.prototype.off = function (type, f) {
    off(this, type, f);
  };
}

// Due to the fact that we still support jurassic IE versions, some
// compatibility wrappers are needed.

export function e_preventDefault(e) {
  if (e.preventDefault) e.preventDefault();
  else e.returnValue = false;
}
export function e_stopPropagation(e) {
  if (e.stopPropagation) e.stopPropagation();
  else e.cancelBubble = true;
}
export function e_defaultPrevented(e) {
  return e.defaultPrevented != null ? e.defaultPrevented : e.returnValue == false;
}
export function e_stop(e) {
  e_preventDefault(e);
  e_stopPropagation(e);
}

export function e_target(e) {
  return e.target || e.srcElement;
}
export function e_button(e) {
  let b = e.which;
  if (b == null) {
    if (e.button & 1) b = 1;
    else if (e.button & 2) b = 3;
    else if (e.button & 4) b = 2;
  }
  if (mac && e.ctrlKey && b == 1) b = 3;
  return b;
}
